/**
 * 
 */
package com.googlecode.flair.flexsdk;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * @author ccPascu
 * 
 */
public class FlexSdkIntrospector {

	private static Element flexComponents = new Element("flex-components");

	private static String sdkLocation;
	
	private Map<String, FlexComponentClass> parsedClasses = new Hashtable<String, FlexComponentClass>();
	
	public static void main(String[] args) throws IOException {
		FlexSdkIntrospector introspector = new FlexSdkIntrospector();
		sdkLocation = "C:/Program Files/Adobe/Flex Builder 3/sdks/3.0.0/frameworks/projects/framework/src";
		File sdkFolder = new File(sdkLocation);

		final FilenameFilter filenameFilter = new FilenameFilter() {
			
			public boolean accept(File dir, String name) {
				return name.endsWith(".as");
			}
		};
		
		introspector.readClass(new File(sdkFolder, "mx/core/UIComponent.as"), "mx.core::");
		introspector.readClass(new File(sdkFolder, "mx/core/Container.as"), "mx.core::");
		
		introspector.readClass(new File(sdkFolder, "mx/core/ScrollControlBase.as"), "mx.core::");
		introspector.readClass(new File(sdkFolder, "mx/controls/listClasses/ListBase.as"), "mx.controls.listClasses::");
		introspector.readClass(new File(sdkFolder, "mx/controls/dataGridClasses/DataGridBase.as"), "mx.controls.dataGridClasses::");
		
		
		introspector.readClass(new File(sdkFolder, "mx/controls/listClasses/TileBase.as"), "mx.controls.listClasses::");
		introspector.readClass(new File(sdkFolder, "mx/controls/scrollClasses/ScrollBar.as"), "mx.controls.scrollClasses::");
		introspector.readClass(new File(sdkFolder, "mx/controls/sliderClasses/Slider.as"), "mx.controls.sliderClasses::");
		
		File containersFolder = new File(sdkFolder, "mx/containers");
		final File[] containerFiles = containersFolder.listFiles(filenameFilter);
		for (File containerFile : containerFiles) {
			introspector.readContainerFile(containerFile);
		}

		File componentsFolder = new File(sdkFolder, "mx/controls");
		final File[] componentsFiles = componentsFolder.listFiles(filenameFilter);
		for (File componentFile : componentsFiles) {
			introspector.readComponentFile(componentFile);
		}
		
		XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
		final FileOutputStream fos = new FileOutputStream("./FlexComponents.xml");
		outputter.output(flexComponents, fos);
		fos.close();
		
		
		//introspector.generateFiles();
		introspector.generateMetainformation();
		introspector.generateStatistics();
	}
	
	
	public final void generateStatistics() throws IOException {
		Set<String> set = new HashSet<String>();
		final Map<String, Integer> usage = new Hashtable<String, Integer>();
		for (FlexComponentClass componentClass : parsedClasses.values()) {
			List<FlexStyle> styles = componentClass.getAllStyles();
			for (FlexStyle flexStyle : styles) {
				final String name = flexStyle.getName();
				set.add(name);
				if (usage.get(name) == null) {
					usage.put(name, 1);
				} else {
					usage.put(name, usage.get(name) + 1);
				}
			}
		}
		final String[] names = set.toArray(new String[] {});
		Arrays.sort(names, new Comparator<String>() {
			public int compare(String o1, String o2) {
				return usage.get(o2).compareTo(usage.get(o1));
			}
		});
		final PrintWriter fos = new PrintWriter(new FileWriter("./StylesUsage.xml"));
		for (String name : names) {
			if (name.endsWith("Skin")) {
				continue;
			}
			fos.printf("%s - %s \n", name, usage.get(name));
		}
		fos.close();
	}
	
	public final void generateMetainformation() { // Fake name
		
		try {
			
			Document metadata = new Document();
			final Element root = new Element("flex-components");
			metadata.setRootElement(root);
			final Element stylesElement = new Element("styles");
			root.addContent(stylesElement);
			Map<String, FlexStyle> allStyles = new Hashtable<String, FlexStyle>();
			List<String> styleNames = new Vector<String>();
			for (FlexComponentClass componentClass : parsedClasses.values()) {
				List<FlexStyle> componentStyles = componentClass.getStyles();
				for (FlexStyle flexStyle : componentStyles) {
					final String styleName = flexStyle.getName();
					allStyles.put(styleName, flexStyle);
					if (!styleNames.contains(styleName)) {
						styleNames.add(styleName);
					}
				}
			}
			
			Collections.sort(styleNames);
			for (String styleName : styleNames) {
				if (styleName.endsWith("Skin")) {
					continue;
				}
				stylesElement.addContent(allStyles.get(styleName).toXml());
			}
			
			XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
			final FileOutputStream fos = new FileOutputStream("./FlexComponents2.xml");
			outputter.output(metadata, fos);
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	
	public final void generateFiles() {
		Configuration configuration = new Configuration();
		try {
			configuration.setDirectoryForTemplateLoading(new File("./templates"));
			Template template = configuration.getTemplate("component-class.ftl");
			int files = 0;
			for (FlexComponentClass componentClass : parsedClasses.values()) {
				Map<String, FlexComponentClass> root = new Hashtable<String, FlexComponentClass>();
				root.put("flexClass", componentClass);
				final FileWriter writer = new FileWriter("./generated/" + componentClass.getClassName() + "Editor.as");
				template.process(root, writer);
				writer.close();
				files++;
				if (files == 5) {
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TemplateException e) {
			e.printStackTrace();
		}
		
	}

	private void readComponentFile(File componentFile) {
		final String packageName = "mx.controls::";
		readClass(componentFile, packageName);
	}

	private void readContainerFile(File containerFile) {
		final String packageName = "mx.containers::";
		readClass(containerFile, packageName);
	}

	@SuppressWarnings("unchecked")
	private FlexComponentClass readClass(File classFile, final String packageName) {
		
		excludedStuff = new Hashtable<String, StringBuffer>();
		String className = classFile.getName().substring(0, classFile.getName().lastIndexOf('.'));
		
		FlexComponentClass flexComponentClass = new FlexComponentClass(packageName, className);
		try {	
			
			BufferedReader reader = new BufferedReader(new FileReader(classFile));
			String line = null;
			
			String arrayType = null;
			
			StringBuffer asdoc = null;
			String extendedClass = "";
			while ((line = reader.readLine()) != null) {
				if (line.indexOf("[Style(") > -1) {
					FlexStyle style = readStyle(line);
					style.setDocumentation(asdoc.toString());
					flexComponentClass.addStyle(style);
				}
				if (line.indexOf("[Exclude(") > -1) {
					addExclusion(readProperties(line, "[Exclude("));
				}
				
				if (line.indexOf("[Inspectable(") > -1) {
					Map<String, String> inspectable = readProperties(line, "[Inspectable(");
					if (inspectable.containsKey("arrayType")) {
						arrayType = inspectable.get("arrayType");
					}
				} else if (arrayType != null && line.indexOf("public function get ") > -1 && line.trim().endsWith("Array")) {
					String property = line.substring(line.indexOf(" get ") + 5, line.indexOf("("));
					flexComponentClass.addPropertyArrayType(property, arrayType);
					if (arrayType.lastIndexOf('.') > -1) {
						readClass(new File(sdkLocation, arrayType.replace('.', '/') + ".as"), arrayType.substring(0, arrayType.lastIndexOf('.')) + "::");
					}
					arrayType = null;
				}
				
				final int indexOfExtends = line.indexOf(" extends ");
				if (line.startsWith("public class ") && indexOfExtends > -1) {
					int extendedClassNameStart = indexOfExtends + " extends ".length();
					final int extendedClassNameEnd = line.indexOf(' ', extendedClassNameStart);
					extendedClass = line.substring(extendedClassNameStart, extendedClassNameEnd > -1 ? extendedClassNameEnd : line.length());
					//component.setAttribute("extends", packageName + extendedClass);
					if (!parsedClasses.containsKey(extendedClass) 
							&& !"FlexSprite".equals(extendedClass) 
							&& !"CSSStyleDeclaration".equals(extendedClass) 
							&& !"EventDispatcher".equals(extendedClass)) {
						FlexComponentClass parent = readClass(new File(classFile.getParentFile(), extendedClass + ".as"), packageName);
						flexComponentClass.setParent(parent);
					} else if (parsedClasses.containsKey(extendedClass)) {
						FlexComponentClass parent = parsedClasses.get(extendedClass);
						flexComponentClass.setParent(parent);
					}
				}
				if (line.startsWith("include \"../styles/metadata/")) {
					readStyles(flexComponentClass, new File(sdkLocation, "mx" + line.substring(line.indexOf("..") + 2, line.lastIndexOf("\""))));
				}
				
				
				// As Documentation
				if (line.startsWith("/**")) {
					asdoc = new StringBuffer();
				}
				if (line.startsWith(" *  ") && asdoc != null) {
					asdoc.append(line.substring(" *  ".length())).append(" \n");
				}
				if (line.startsWith(" */")) {
					// asdoc ended.
				}
			}
			
			
			flexComponentClass.setExclusions(excludedStuff);
			
			final Element component = flexComponentClass.toXml();
			
			if (!parsedClasses.containsKey(className)) {
				if (!(packageName.contains("Classes") || className.endsWith("Base"))) {
					flexComponents.addContent(component);
				}
				parsedClasses.put(className, flexComponentClass);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return flexComponentClass;
	}
	
	

	private void readStyles(FlexComponentClass flexComponentClass, File file) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			StringBuffer asdoc = null;
			String line = null;
			while ((line = reader.readLine()) != null) {
				if (line.indexOf("[Style(") > -1) {
					FlexStyle style = readStyle(line);
					style.setDocumentation(asdoc.toString());
					flexComponentClass.addStyle(style);
				}
				// As Documentation
				if (line.startsWith("/**")) {
					asdoc = new StringBuffer();
				}
				if (line.startsWith(" *  ") && asdoc != null) {
					asdoc.append(line.substring(" *  ".length())).append(" \n");
				}
				if (line.startsWith(" */")) {
					// asdoc ended.
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private FlexStyle readStyle(String line) {
		String tokenName = "[Style(";
		Map<String, String> properties = readProperties(line, tokenName);
		FlexStyle style = new FlexStyle(properties);
		return style;
	}

	private Map<String, String> readProperties(String line, String tokenName) {
		Map<String, String> props = new Hashtable<String, String>();
		String[] properties = line.substring(line.indexOf(tokenName) + tokenName.length(), line.lastIndexOf(")]")).replaceAll("\", ", "\"\n").split("\n");
		for (String property : properties) {
			String name = property.substring(0, property.indexOf('='));
			String value = property.substring(property.indexOf('"') + 1, property.lastIndexOf('"'));
			props.put(name, value);
		}
		return props;
	}

	
	private Map<String, StringBuffer> excludedStuff = new Hashtable<String, StringBuffer>();
	private void addExclusion(Map<String, String> exclusion) {
		String name = exclusion.get("name");
		String kind = exclusion.get("kind");
//		if (!"style".equals(kind)) {
//			return;
//		}
		if (excludedStuff.get(kind) == null) {
			excludedStuff.put(kind, new StringBuffer());
		} 
		if (excludedStuff.get(kind).length() > 0) {
			excludedStuff.get(kind).append(" ");
		}
		excludedStuff.get(kind).append(name);
	}
	
}
